package main

import (
	"context"
	"crypto/sha256"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strings"
	"time"

	"gopkg.in/yaml.v2"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
)

const (
	defaultConfigMapName      = "alert-rule"
	defaultNamespace          = "default"
	configMapDataKey          = "prometheus_alert_rules.yml"
	syncInterval              = 1 * time.Minute
	thanosRulerServiceName    = "thanos-ruler-headless"
	thanosRulerReloadEndpoint = "/-/reload"
	maxRetries                = 3
	retryDelay                = 2 * time.Second
)

type SyncService struct {
	kubeClient     kubernetes.Interface
	namespace      string
	configMapName  string
	alertRulesURL  string
	httpClient     *http.Client
	lastConfigHash string
}

func NewSyncService() (*SyncService, error) {
	// 创建 Kubernetes 客户端（集群内配置）
	config, err := rest.InClusterConfig()
	if err != nil {
		return nil, fmt.Errorf("error creating in-cluster config: %v", err)
	}

	kubeClient, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, fmt.Errorf("error creating kubernetes client: %v", err)
	}

	// 获取配置
	namespace := os.Getenv("NAMESPACE")
	if namespace == "" {
		namespace = defaultNamespace
	}

	configMapName := os.Getenv("CONFIGMAP_NAME")
	if configMapName == "" {
		configMapName = defaultConfigMapName
	}

	alertRulesURL := os.Getenv("ALERT_RULES_URL")
	if alertRulesURL == "" {
		alertRulesURL = "https://devops.switchbot.work/api/alert/rule/?format=json&no_page=true"
	}

	// 创建 HTTP 客户端
	httpClient := &http.Client{
		Timeout: 30 * time.Second,
	}

	return &SyncService{
		kubeClient:    kubeClient,
		namespace:     namespace,
		configMapName: configMapName,
		alertRulesURL: alertRulesURL,
		httpClient:    httpClient,
	}, nil
}

func (s *SyncService) fetchAlertRules() ([]AlertRule, error) {
	// Make HTTP GET request
	resp, err := s.httpClient.Get(s.alertRulesURL)
	if err != nil {
		return nil, fmt.Errorf("error making request: %v", err)
	}
	defer resp.Body.Close()

	// Read response body
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("error reading response: %v", err)
	}

	// Parse JSON into slice of AlertRule
	var rules []AlertRule
	if err := json.Unmarshal(body, &rules); err != nil {
		return nil, fmt.Errorf("error parsing JSON: %v", err)
	}

	return rules, nil
}

func (s *SyncService) convertToPrometheusRules(rules []AlertRule) PrometheusRuleGroups {
	groups := make([]PrometheusRuleGroup, 0, len(rules))

	for _, rule := range rules {
		if !rule.IsActive {
			continue
		}

		// Extract user names, lark union IDs, and lark emails
		var userNames []string
		var larkUnionIDs []string
		var larkEmails []string
		for _, user := range rule.NotifyUsersDetail {
			userNames = append(userNames, user.Name)
			larkUnionIDs = append(larkUnionIDs, user.LarkUnionID)
			larkEmails = append(larkEmails, user.LarkEmail)
		}

		// Create a single rule group for each rule
		ruleGroup := PrometheusRuleGroup{
			Name: rule.Name,
			Rules: []PrometheusRule{
				{
					Alert: rule.Name,
					Expr:  rule.Expr,
					For:   rule.ForValue,
					Labels: map[string]string{
						"severity": rule.Level,
					},
					Annotations: map[string]string{
						"description":                rule.Description,
						"notify_user_names":          strings.Join(userNames, ","),
						"notify_user_lark_union_ids": strings.Join(larkUnionIDs, ","),
						"notify_user_lark_emails":    strings.Join(larkEmails, ","),
						"notify_group_test_id":       rule.NotifyGroupTestID,
						"notify_group_test_name":     rule.NotifyGroupTestName,
						"notify_group_prod_id":       rule.NotifyGroupProdID,
						"notify_group_prod_name":     rule.NotifyGroupProdName,
						"graph_expr":                 rule.GraphExpr,
					},
				},
			},
		}
		groups = append(groups, ruleGroup)
	}

	return PrometheusRuleGroups{
		Groups: groups,
	}
}

// calculateContentHash 计算内容的SHA256哈希值
func (s *SyncService) calculateContentHash(content string) string {
	hash := sha256.Sum256([]byte(content))
	return fmt.Sprintf("%x", hash)
}

// hasContentChanged 检查内容是否发生变化
func (s *SyncService) hasContentChanged(newContent string) bool {
	newHash := s.calculateContentHash(newContent)
	if s.lastConfigHash == "" || s.lastConfigHash != newHash {
		s.lastConfigHash = newHash
		return true
	}
	return false
}

func (s *SyncService) ensureConfigMapExists(ctx context.Context) error {
	_, err := s.kubeClient.CoreV1().ConfigMaps(s.namespace).Get(ctx, s.configMapName, metav1.GetOptions{})
	if err == nil {
		// ConfigMap already exists
		return nil
	}

	if !errors.IsNotFound(err) {
		return fmt.Errorf("error checking configmap existence: %v", err)
	}

	// Create ConfigMap
	configMap := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:      s.configMapName,
			Namespace: s.namespace,
			Labels: map[string]string{
				"app": "switchbot-alert-rule-sync",
			},
		},
		Data: map[string]string{
			configMapDataKey: "",
		},
	}

	_, err = s.kubeClient.CoreV1().ConfigMaps(s.namespace).Create(ctx, configMap, metav1.CreateOptions{})
	if err != nil {
		return fmt.Errorf("error creating configmap: %v", err)
	}

	log.Printf("ConfigMap %s created in namespace %s", s.configMapName, s.namespace)
	return nil
}

func (s *SyncService) updateConfigMapWithRules(ctx context.Context, promRules PrometheusRuleGroups) (bool, error) {
	// Convert rules to YAML
	yamlData, err := yaml.Marshal(promRules)
	if err != nil {
		return false, fmt.Errorf("error marshaling to YAML: %v", err)
	}

	// Check if content has changed
	if !s.hasContentChanged(string(yamlData)) {
		log.Println("ConfigMap content unchanged, skipping update")
		return false, nil
	}

	// Get existing ConfigMap
	configMap, err := s.kubeClient.CoreV1().ConfigMaps(s.namespace).Get(ctx, s.configMapName, metav1.GetOptions{})
	if err != nil {
		return false, fmt.Errorf("error getting configmap: %v", err)
	}

	// Update ConfigMap data
	if configMap.Data == nil {
		configMap.Data = make(map[string]string)
	}
	configMap.Data[configMapDataKey] = string(yamlData)

	// Update ConfigMap
	_, err = s.kubeClient.CoreV1().ConfigMaps(s.namespace).Update(ctx, configMap, metav1.UpdateOptions{})
	if err != nil {
		return false, fmt.Errorf("error updating configmap: %v", err)
	}

	log.Printf("ConfigMap %s/%s updated successfully", s.namespace, s.configMapName)
	return true, nil
}

// getThanosRulerEndpoints 获取Thanos Ruler的所有端点
func (s *SyncService) getThanosRulerEndpoints(ctx context.Context) ([]string, error) {
	endpoints, err := s.kubeClient.CoreV1().Endpoints(s.namespace).Get(ctx, thanosRulerServiceName, metav1.GetOptions{})
	if err != nil {
		return nil, fmt.Errorf("error getting endpoints for service %s: %v", thanosRulerServiceName, err)
	}

	var rulerEndpoints []string
	for _, subset := range endpoints.Subsets {
		for _, address := range subset.Addresses {
			for _, port := range subset.Ports {
				// 只使用HTTP端口进行重载，跳过gRPC端口
				if port.Name == "http" {
					endpoint := fmt.Sprintf("http://%s:%d%s", address.IP, port.Port, thanosRulerReloadEndpoint)
					rulerEndpoints = append(rulerEndpoints, endpoint)
				}
			}
		}
	}

	return rulerEndpoints, nil
}

// reloadThanosRuler 重新加载Thanos Ruler配置，带重试机制
func (s *SyncService) reloadThanosRuler(ctx context.Context, endpoint string) error {
	var lastErr error

	for attempt := 1; attempt <= maxRetries; attempt++ {
		req, err := http.NewRequestWithContext(ctx, "POST", endpoint, nil)
		if err != nil {
			lastErr = fmt.Errorf("error creating request: %v", err)
			continue
		}

		resp, err := s.httpClient.Do(req)
		if err != nil {
			lastErr = fmt.Errorf("error making request to %s (attempt %d/%d): %v", endpoint, attempt, maxRetries, err)
			if attempt < maxRetries {
				log.Printf("Request failed, retrying in %v... %v", retryDelay, lastErr)
				time.Sleep(retryDelay)
			}
			continue
		}

		resp.Body.Close()

		if resp.StatusCode >= 200 && resp.StatusCode < 300 {
			log.Printf("Successfully reloaded Thanos ruler at %s", endpoint)
			return nil
		}

		lastErr = fmt.Errorf("received non-2xx status code %d from %s (attempt %d/%d)", resp.StatusCode, endpoint, attempt, maxRetries)
		if attempt < maxRetries {
			log.Printf("Request failed with status %d, retrying in %v...", resp.StatusCode, retryDelay)
			time.Sleep(retryDelay)
		}
	}

	return fmt.Errorf("failed to reload Thanos ruler after %d attempts: %v", maxRetries, lastErr)
}

// reloadAllThanosRulers 重新加载所有Thanos Ruler实例
func (s *SyncService) reloadAllThanosRulers(ctx context.Context) error {
	endpoints, err := s.getThanosRulerEndpoints(ctx)
	if err != nil {
		return fmt.Errorf("error getting Thanos ruler endpoints: %v", err)
	}

	if len(endpoints) == 0 {
		log.Printf("No Thanos ruler endpoints found for service %s", thanosRulerServiceName)
		return nil
	}

	log.Printf("Found %d Thanos ruler endpoints, triggering reload...", len(endpoints))

	var errors []string
	for _, endpoint := range endpoints {
		if err := s.reloadThanosRuler(ctx, endpoint); err != nil {
			errors = append(errors, err.Error())
		}
	}

	if len(errors) > 0 {
		return fmt.Errorf("errors occurred while reloading rulers: %s", strings.Join(errors, "; "))
	}

	log.Printf("Successfully reloaded all %d Thanos ruler instances", len(endpoints))
	return nil
}

func (s *SyncService) syncOnce(ctx context.Context) error {
	// Ensure ConfigMap exists
	if err := s.ensureConfigMapExists(ctx); err != nil {
		return fmt.Errorf("error ensuring configmap exists: %v", err)
	}

	// Fetch alert rules
	rules, err := s.fetchAlertRules()
	if err != nil {
		return fmt.Errorf("error fetching alert rules: %v", err)
	}

	// Convert to Prometheus format
	promRules := s.convertToPrometheusRules(rules)

	// Update ConfigMap
	updated, err := s.updateConfigMapWithRules(ctx, promRules)
	if err != nil {
		return fmt.Errorf("error updating configmap: %v", err)
	}

	// If ConfigMap was updated, reload Thanos ruler
	if updated {
		if err := s.reloadAllThanosRulers(ctx); err != nil {
			log.Printf("Warning: Failed to reload Thanos rulers: %v", err)
			// Don't return error here as ConfigMap update was successful
		}
	}

	log.Printf("Successfully synced %d alert rules to ConfigMap %s/%s",
		len(promRules.Groups), s.namespace, s.configMapName)
	return nil
}

func (s *SyncService) Run(ctx context.Context) {
	log.Printf("Starting Switchbot Alert Rules Sync Service...")
	log.Printf("Target: ConfigMap %s/%s", s.namespace, s.configMapName)
	log.Printf("Source: %s", s.alertRulesURL)
	log.Printf("Sync interval: %v", syncInterval)

	ticker := time.NewTicker(syncInterval)
	defer ticker.Stop()

	// 首次执行
	if err := s.syncOnce(ctx); err != nil {
		log.Printf("Error during initial sync: %v", err)
	}

	// 循环执行
	for {
		select {
		case <-ctx.Done():
			log.Println("Sync service stopped")
			return
		case <-ticker.C:
			if err := s.syncOnce(ctx); err != nil {
				log.Printf("Error during sync: %v", err)
			}
		}
	}
}

func main() {
	syncService, err := NewSyncService()
	if err != nil {
		log.Fatalf("Error creating sync service: %v", err)
	}

	ctx := context.Background()
	syncService.Run(ctx)
}
